package com.fary.boot.builder;

import com.fary.context.ApplicationContext;
import com.fary.context.ApplicationContextAware;
import com.fary.context.ApplicationListener;
import com.fary.context.ConfigurableApplicationContext;
import com.fary.boot.builder.ParentContextApplicationContextInitializer.ParentContextAvailableEvent;
import com.fary.context.event.ContextClosedEvent;
import com.fary.core.Ordered;
import com.fary.core.SpringException;
import com.fary.util.ObjectUtils;

import java.lang.ref.WeakReference;

/**
 * 父容器关闭时会发布ContextClosedEvent事件
 */
public class ParentContextCloserApplicationListener implements ApplicationListener<ParentContextAvailableEvent>, ApplicationContextAware, Ordered {

    private int order = Ordered.LOWEST_PRECEDENCE - 10;

    private ApplicationContext context;

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws SpringException {
        this.context = context;
    }

    // 当一个应用上下文中发出ParentContextAvailableEvent 事件时，表明该应用上下文被设定了双亲
    // 应用上下文并且自己已经可用(refreshed),现在在该应用上下文的双亲上下文中登记一个事件监听器，
    // 用于监听双亲上下文的关闭事件，从而关闭该子应用上下文。
    @Override
    public void onApplicationEvent(ParentContextAvailableEvent event) {
        maybeInstallListenerInParent(event.getApplicationContext());
    }

    private void maybeInstallListenerInParent(ConfigurableApplicationContext child) {
        if (child == this.context && child.getParent() instanceof ConfigurableApplicationContext) {
            ConfigurableApplicationContext parent = (ConfigurableApplicationContext) child.getParent();
            parent.addApplicationListener(createContextCloserListener(child));
        }
    }

    /**
     * Subclasses may override to create their own subclass of ContextCloserListener. This
     * still enforces the use of a weak reference.
     *
     * @param child the child context
     * @return the {@link ContextCloserListener} to use
     */
    protected ContextCloserListener createContextCloserListener(ConfigurableApplicationContext child) {
        return new ContextCloserListener(child);
    }

    /**
     * {@link ApplicationListener} to close the context.
     */
    protected static class ContextCloserListener implements ApplicationListener<ContextClosedEvent> {

        private WeakReference<ConfigurableApplicationContext> childContext;

        public ContextCloserListener(ConfigurableApplicationContext childContext) {
            this.childContext = new WeakReference<>(childContext);
        }

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            ConfigurableApplicationContext context = this.childContext.get();
            if ((context != null) && (event.getApplicationContext() == context.getParent()) && context.isActive()) {
                context.close();
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (obj instanceof ContextCloserListener) {
                ContextCloserListener other = (ContextCloserListener) obj;
                return ObjectUtils.nullSafeEquals(this.childContext.get(), other.childContext.get());
            }
            return super.equals(obj);
        }

        @Override
        public int hashCode() {
            return ObjectUtils.nullSafeHashCode(this.childContext.get());
        }

    }

}